Workbench creation/ru

Вступление

В данном материале изложена информация о том, как создать новый верстак для FreeCAD и добавить его в интерфейс. Верстаки являются контейнерами для команд FreeCAD. Они могут быть написаны на Python или на C++ или в сочетании того и другого, что имеет преимущество в сочетании скорости C++ и гибкости Python. Однако во всех случаях ваш верстак будет запущен через два специальных Python файла. Это касается, как "внутренних" верстаков, входящих в дистрибутив FreeCAD, так и "внешних", распространяемых через Менеджер дополнений или устанавливаемыми вручную путем загрузки из какого-либо онлайн-репозитория. Внутренние верстаки могут быть написаны либо на C++, либо на Python, либо на их комбинации, в то время как внешние верстаки могут быть написаны, только на Python.

Структура Верстака

Вам нужна папка с любым именем, размещённая в каталоге User Mod, с файлом Init.py и, опционально, файлом InitGui.py. Файл Init.py выполняется при запуске FreeCAD, а файл InitGui.py - сразу после, но только при запуске FreeCAD в режиме GUI. Это всё, что нужно для того, чтобы FreeCAD нашёл ваш верстак при запуске и добавил его в свой интерфейс.

Каталог user Mod является подкаталогом каталога данных пользовательского приложения (последний можно найти, набрав App.getUserAppDataDir() в консоли Python):

Каталог Mod должен выглядеть так:

/Mod/
 +-- MyWorkbench/
     +-- Init.py
     +-- InitGui.py

Внутри этих файлов вы можете делать всё, что захотите. Обычно они используются следующим образом:

Структура и содержимое файлов верстака, описанные здесь, являются классическим способом создания нового верстака. Можно использовать небольшую вариацию структуры файлов при создании нового верстака на Python. Этот альтернативный способ лучше всего описать как «верстак с разметкой имен», открывающий возможность использовать pip для установки верстака. Обе структуры работают, так что это скорее вопрос предпочтений при создании нового верстака. Представленные здесь стиль и структура верстаков доступны в глобальном пространстве имён FreeCAD, в то время как при альтернативном стиле и структуре верстак находится в выделенном пространстве имён. Другие материалы по теме смотрите в разделе Сопутствующей информации.

Структура верстака на языке C++

Если вы собираетесь создать Верстак на python, вам не нужно проявлять особую осторожность, и вы можете просто разместить другие файлы python в папке с Init.py и InitGui.py. Однако при работе с C++ вам следует проявлять большую осторожность и начинать соблюдать одно фундаментальное правило FreeCAD: Разделение вашего рабочего стола между частью приложения (которая может работать в режиме консоли, без какого-либо элемента графического интерфейса) и частью графического интерфейса, которая будет загружена только при запуске FreeCAD с полной графической средой. Поэтому при создании верстака на C++ вы, скорее всего, будете использовать два модуля: модуль самого приложения и модуль графического интерфейса. Эти два модуля, конечно, должны быть вызываемы из python. Любой модуль FreeCAD (приложение или графический интерфейс) состоит, по крайней мере, из файла инициализации модуля. Это типичный AppMyModuleGui.cpp файл:

extern "C" {
    void MyModuleGuiExport initMyModuleGui()  
    {
         if (!Gui::Application::Instance) {
            PyErr_SetString(PyExc_ImportError, "Cannot load Gui module in console application.");
            return;
        }
        try {
            // import other modules this one depends on
            Base::Interpreter().runString("import PartGui");
            // run some python code in the console
            Base::Interpreter().runString("print('welcome to my module!')");
        }
        catch(const Base::Exception& e) {
            PyErr_SetString(PyExc_ImportError, e.what());
            return;
        }
        (void) Py_InitModule("MyModuleGui", MyModuleGui_Import_methods);   /* mod name, table ptr */
        Base::Console().Log("Loading GUI of MyModule... done\n");
    
        // initializes the FreeCAD commands (in another cpp file)
        CreateMyModuleCommands();
    
        // initializes workbench and object definitions
        MyModuleGui::Workbench::init();
        MyModuleGui::ViewProviderSomeCustomObject::init();
    
         // add resources and reloads the translators
        loadMyModuleResource();
    }
}

Файл Init.py

"""FreeCAD init script of XXX module"""

# ***************************************************************************
# *   Copyright (c) 2015 John Doe john@doe.com                              *   
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENSE text file.                                 *
# *                                                                         *
# *   FreeCAD is distributed in the hope that it will be useful,            *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Lesser General Public License for more details.                   *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with FreeCAD; if not, write to the Free Software        *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************/

FreeCAD.addImportType("My own format (*.own)", "importOwn")
FreeCAD.addExportType("My own format (*.own)", "exportOwn")
print("I am executing some stuff here when FreeCAD starts!")

Вы можете выбрать любую понравившуюся лицензию для вашего верстака, но имейте в виду, что если вы хотите, чтобы ваш верстак в какой-то момент был интегрирован в исходный код FreeCAD и распространялся вместе с ним, он должен быть LGPL2 +, как в примере выше. Смотрите Лицензирование.

Функции FreeCAD.addImportType() и addEXportType() позволяют указать имя и расширение типа файла, а также модуль Python, отвечающий за его импорт. В примере выше модуль importOwn.py будет работать с файлами .own. Дополнительные примеры смотрите в Фрагментах кода.

Python верстаки

InitGui.py файл:

class MyWorkbench (Workbench):

    MenuText = "My Workbench"
    ToolTip = "A description of my workbench"
    Icon = """paste here the contents of a 16x16 xpm icon"""

    def Initialize(self):
        """This function is executed when the workbench is first activated.
        It is executed once in a FreeCAD session followed by the Activated function.
        """
        import MyModuleA, MyModuleB # import here all the needed files that create your FreeCAD commands
        self.list = ["MyCommand1", "MyCommand2"] # a list of command names created in the line above
        self.appendToolbar("My Commands", self.list) # creates a new toolbar with your commands
        self.appendMenu("My New Menu", self.list) # creates a new menu
        self.appendMenu(["An existing Menu", "My submenu"], self.list) # appends a submenu to an existing menu

    def Activated(self):
        """This function is executed whenever the workbench is activated"""
        return

    def Deactivated(self):
        """This function is executed whenever the workbench is deactivated"""
        return

    def ContextMenu(self, recipient):
        """This function is executed whenever the user right-clicks on screen"""
        # "recipient" will be either "view" or "tree"
        self.appendContextMenu("My commands", self.list) # add commands to the context menu

    def GetClassName(self): 
        # This function is mandatory if this is a full Python workbench
        # This is not a template, the returned string should be exactly "Gui::PythonWorkbench"
        return "Gui::PythonWorkbench"
       
Gui.addWorkbench(MyWorkbench())

Кроме этого, вы можете делать все, что захотите: если хотите, вы могли бы поместить весь код своего верстака внутрь InitGui.py, но обычно удобнее размещать различные функции вашего верстака в отдельных файлах. Таким образом, эти файлы меньше по размеру и их легче читать. После чего вы можете импортировать эти файлы в InitGui.py файл. Также, вы можете упорядочить файлы, как пожелаете, хорошим примером является создавать по одному отдельному файлу для каждой добавляемой вами команды FreeCAD.

Раздел настроек верстака

Вы можете добавить страницу настроек для вашего Python верстака. Страницы настроек ищут значок с определенным именем в системе ресурсов Qt. Если вашего значка нет в системе ресурсов или у него неправильное имя, ваш значок не будет отображаться в окне настроек.

Добавление значка для вашего верстака:

При добавлении или изменении значков, следует повторить вышеперечисленные шаги.

@kbwbe создал хороший скрипт для компиляции ресурсов для верстака A2Plus. Смотрите ниже.

Добавление страницы (страниц) настроек:

Публикация и распространение

Для распространения вашего Python-верстака вы можете либо просто разместить файлы в каком-то месте и поручить пользователям скачать их и поместить в каталог Mod вручную, либо разместить их в онлайн git-репозитории (GitHub, GitLab, Framagit и Debian Salsa поддерживаются в настоящее время) и настроить их для установки через Менеджер дополнений. Инструкции по включению в официальный список дополнений FreeCAD можно найти на странице FreeCAD репозиторий дополнений на GitHub. Для использования Менеджера дополнений необходимо включить файл метаданных package.xml, который указывает Менеджеру дополнений, как найти иконку вашего верстака, а также позволяет отображать описание, номер версии и т.д. Он также может быть использован для указания других верстаков или пакетов Python, от которых зависит ваш верстак, которые блокируются им или призваны заменить его.

Краткое руководство по созданию базового файла package.xml и добавлению верстака в Менеджер дополнений смотри здесь: Добавление Верстака в Менеджер дополнений.

По желанию вы можете включить отдельный файл метаданных, описывающий ваши зависимости в Python. Это может быть либо файл с именем metadata.txt, описывающий внешние зависимости вашего верстака (от других дополнений, верстаков или модулей Python), либо файл requirements.txt, описывающий ваши зависимости от Python. Обратите внимание, что при использовании файла requirements.txt для определения зависимостей используются только имена указанных пакетов: параметры команды pip, параметры include и информация о версии не поддерживаются менеджером дополнений. Пользователи могут вручную запустить файл требований с помощью pip, если эти функции необходимы.

Формат metadata.txt файла представляет собой обычный текст с тремя опциональными строками:

workbenches=
pylibs=
optionalpylibs=

Каждая строка должна состоять из списка элементов, разделённых запятыми, от которых зависит ваш Верстак. Верстаки могут быть как внутренними FreeCAD Верстаками, например, "FEM", так и внешними дополнениями, например, "Curves". Необходимые и необязательные библиотеки Python должны быть указаны с их каноническими именами Python, такими, как вы бы использовали в pip install. Например:

workbenches=FEM,Curves
pylibs=ezdxf
optionalpylibs=metadata,git

Вы также можете включить скрипт, который запускается при удалении вашего пакета. Это файл под названием "uninstall.py", расположенный на верхнем уровне вашего дополнения. Он выполняется, когда пользователь удаляет ваше дополнение с помощью менеджера дополнений. Используйте его, чтобы очистить всё, что ваше дополнение могло сделать с системой пользователя, и что не должно сохраниться после удаления дополнения (например, удаление файлов кэша и т. д.).

Чтобы убедиться, что ваше дополнение правильно читается менеджером дополнений, вы можете включить «режим разработчика», в котором менеджер дополнений проверит все доступные дополнения и убедится, что их метаданные содержат необходимые элементы. Чтобы включить этот режим, выберите: Правка → Настройки... → Менеджер дополнений → Параметры менеджера дополнений → Режим разработчика дополнений, смотри Редактор настроек.

C++ верстаки

Если вы собираетесь разрабатывать верстак на C++, то, вероятно, захотите закодировать и само определение верстака на C++ (хотя это и не обязательно: вы также можете закодировать на C++ только инструменты, а определение верстака оставить на Python). В этом случае файл InitGui.py становится очень простым: Он может содержать всего одну строку:

import MyModuleGui

где MyModule - это ваш полный верстак на C++, включая команды и определение верстака.

Кодирование верстаков на C++ происходит примерно так же. Это типичный файл Workbench.cpp, который нужно включить в Gui часть вашего модуля:

namespace MyModuleGui {
    class MyModuleGuiExport Workbench : public Gui::StdWorkbench
    {
        TYPESYSTEM_HEADER();

    public:
        Workbench();
        virtual ~Workbench();

        virtual void activated();
        virtual void deactivated();

    protected:
        Gui::ToolBarItem* setupToolBars() const;
        Gui::MenuItem*    setupMenuBar() const;
    };
}

Раздел настроек верстака

Вы также можете добавить страницу настроек для верстаков C++. Шаги аналогичны тем, что выполняются в Python.

Публикация и распространение

Есть два варианта распространения верстака на C++: вы можете сами разместить прекомпилированные версии для различных операционных систем, либо попросить о включении вашего кода в исходный код FreeCAD. Как упоминалось выше, для этого требуется лицензия LGPL2+, и вы должны сначала представить свой верстак сообществу на форуме FreeCAD для рассмотрения.

Команды FreeCAD

Команды FreeCAD - это основной строительный блок интерфейса FreeCAD. Они могут появляться в виде кнопок на панелях инструментов и в виде пунктов меню. Но это одна и та же команда. Команда - это простой класс Python, который должен содержать несколько предопределённых атрибутов и функций, определяющих имя команды, её иконку и то, что делать при активации команды.

Определение команд в Python

class My_Command_Class():
    """My new command"""

    def GetResources(self):
        return {"Pixmap"  : "My_Command_Icon", # the name of a svg file available in the resources
                "Accel"   : "Shift+S", # a default shortcut (optional)
                "MenuText": "My New Command",
                "ToolTip" : "What my new command does"}

    def Activated(self):
        """Do something here"""
        return

    def IsActive(self):
        """Here you can define if the command must be active or not (greyed) if certain conditions
        are met or not. This function is optional."""
        return True

FreeCADGui.addCommand("My_Command", My_Command_Class())

Определение команд в С++

Аналогично, вы можете кодировать свои команды на C++, обычно в файле Commands.cpp в вашем модуле Gui. Это типичный файл Commands.cpp:

DEF_STD_CMD_A(CmdMyCommand);

CmdMyCommand::CmdMyCommand()
  :Command("My_Command")
{
  sAppModule    = "MyModule";
  sGroup        = QT_TR_NOOP("MyModule");
  sMenuText     = QT_TR_NOOP("Runs my command...");
  sToolTipText  = QT_TR_NOOP("Describes what my command does");
  sWhatsThis    = QT_TR_NOOP("Describes what my command does");
  sStatusTip    = QT_TR_NOOP("Describes what my command does");
  sPixmap       = "some_svg_icon_from_my_resource";
}

void CmdMyCommand::activated(int iMsg)
{
    openCommand("My Command");
    doCommand(Doc,"print('Hello, world!')");
    commitCommand();
    updateActive();
}

bool CmdMyCommand::isActive(void)
{
  if( getActiveGuiDocument() )
    return true;
  else
    return false;
}

void CreateMyModuleCommands(void)
{
    Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
    rcCmdMgr.addCommand(new CmdMyCommand());
}

"Компиляция" файлов ресурсов

compileA2pResources.py из верстака A2Plus:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2019 kbwbe                                              *
#*                                                                         *
#*   Portions of code based on hamish's assembly 2                         *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENSE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

# This script compiles the A2plus icons for py2 and py3
# For Linux only
# Start this file in A2plus main directory
# Make sure pyside-rcc is installed

import os, glob

qrc_filename = 'temp.qrc'
if os.path.exists(qrc_filename):
    os.remove(qrc_filename)

qrc = '''<RCC>
\t<qresource prefix="/">'''
for fn in glob.glob('./icons/*.svg'):
    qrc = qrc + '\n\t\t<file>%s</file>' % fn
qrc = qrc + '''\n\t</qresource>
</RCC>'''

print(qrc)

f = open(qrc_filename,'w')
f.write(qrc)
f.close()

os.system(
    'pyside-rcc -o a2p_Resources2.py {}'.format(qrc_filename))
os.system(
    'pyside-rcc -py3 -o a2p_Resources3.py {}'.format(qrc_filename))

os.remove(qrc_filename)

Сопутствующая информация